{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义回调\n",
    "自定义回调是一种强大的工具，可以在训练、评估和推理期间自定义 Keras 模型的行为，包括读取/更改 Keras 模型。例子包括 [tf.keras.callbacks.TensorBoard](https://tensorflow.google.cn/api_docs/python/tf/keras/callbacks/TensorBoard)（可使用TensorBoard导出并显示训练进度和结果）或 [tf.keras.callbacks.ModelCheckpoint](https://tensorflow.google.cn/api_docs/python/tf/keras/callbacks/ModelCheckpoint)（在训练过程中自动保存模型）等。\n",
    "\n",
    "在本指南中，将学习什么是 Keras 回调，何时调用，它可以做什么以及如何构建自己的回调。在本指南结束时，将演示如何创建几个简单的回调应用程序，以使你开始自定义回调。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 介绍 Keras 回调\n",
    "在 Keras 中，Callback 是一个 python 类，旨在被子类化以提供特定的功能，在训练的各个阶段（包括批处理/时期开始和结束），测试和预测中调用指定的方法。回调对于在训练期间了解模型的内部状态和统计信息很有用。你可以将回调组成的列表（作为关键字参数回调）传递给 tf.keras.Model.fit()、tf.keras.Model.evaluate() 和 tf.keras.Model.predict() 方法中的任何一个。然后将在训练/评估/推断的不同阶段调用回调方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练: 批次 0 开始于 19:36:04.344742\n",
      "训练: 批次0 结束于 19:36:05.569715\n",
      "训练: 批次 1 开始于 19:36:05.569715\n",
      "训练: 批次1 结束于 19:36:05.571717\n",
      "训练: 批次 2 开始于 19:36:05.571717\n",
      "训练: 批次2 结束于 19:36:05.572718\n",
      "训练: 批次 3 开始于 19:36:05.572718\n",
      "训练: 批次3 结束于 19:36:05.573716\n",
      "训练: 批次 4 开始于 19:36:05.573716\n",
      "训练: 批次4 结束于 19:36:05.574721\n"
     ]
    }
   ],
   "source": [
    "# 为了开始实验，让我们导入 tensorflow 并定义一个简单的 Keras 模型\n",
    "def get_model():\n",
    "    model = tf.keras.Sequential()\n",
    "    model.add(tf.keras.layers.Dense(1, activation='linear', input_dim=784))\n",
    "    model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=0.1), \n",
    "                  loss='mean_squared_error', metrics=['mae'])\n",
    "    return model\n",
    "\n",
    "# 然后，从 Keras datasets API 加载 MNIST 数据集用于训练和测试\n",
    "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n",
    "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
    "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
    "\n",
    "# 现在，定义一个简单的自定义回调来跟踪每批数据的开始和结束。在调用期间，它打印当前批处理的索引。\n",
    "import datetime\n",
    "\n",
    "class MyCustomCallback(tf.keras.callbacks.Callback):\n",
    "  def on_train_batch_begin(self, batch, logs=None):\n",
    "    print('训练: 批次 {} 开始于 {}'.format(batch, datetime.datetime.now().time()))\n",
    "  def on_train_batch_end(self, batch, logs=None):\n",
    "    print('训练: 批次{} 结束于 {}'.format(batch, datetime.datetime.now().time()))\n",
    "  def on_test_batch_begin(self, batch, logs=None):\n",
    "    print('评估: 批次 {} 开始于 {}'.format(batch, datetime.datetime.now().time()))\n",
    "  def on_test_batch_end(self, batch, logs=None):\n",
    "    print('评估: 批次 {} 结束于 {}'.format(batch, datetime.datetime.now().time()))\n",
    "    \n",
    "# 为 model 方法(如 tf.keras.Model.fit() )提供回调以确保在这些阶段调用这些方法:\n",
    "model = get_model()\n",
    "_ = model.fit(x_train, y_train,\n",
    "          batch_size=64,\n",
    "          epochs=1,\n",
    "          steps_per_epoch=5,\n",
    "          verbose=0,\n",
    "          callbacks=[MyCustomCallback()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 接受回调的模型方法\n",
    "用户可以向以下tf.keras提供一个回调列表：\n",
    "1. [fit()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#fit), [fit_generator()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#fit_generator)\n",
    "    \n",
    "    为固定数量的 epoch （数据集上的迭代，或由 python 生成器逐批生成的数据）训练模型。\n",
    "2. [evaluate()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#evaluate),[evaluate_generator()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#evaluate_generator)\n",
    "    \n",
    "    计算给定数据或数据生成器的模型。输出评估中的损失和度量值。\n",
    "3. [predict()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#predict), [predict_generator()](https://tensorflow.google.cn/api_docs/python/tf/keras/Model#predict_generator)\n",
    "    \n",
    "    为输入数据或数据生成器生成输出预测。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=5,\n",
    "          callbacks=[MyCustomCallback()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 回调方法的概述\n",
    "### 训练/测试/预测的常用方法\n",
    "对于训练、测试、预测，Keras 提供了以下方法：\n",
    "1. on_(train|test|predict)_begin(self, logs=None)：在拟合/评估/预测开始时调用。\n",
    "2. on_(train|test|predict)_end(self, logs=None)：在拟合/评估/预测结束时调用。\n",
    "3. on_(train|test|predict)_batch_begin(self, batch, logs=None)：在训练/测试/预测过程中，在处理批处理之前调用。在此方法中，日志是一个包含批处理和大小可用键的 dict，表示当前的批号和批处理的大小。\n",
    "4. on_(train|test|predict)_batch_end(self, batch, logs=None)：在训练/测试/预测一批结束时调用。在这个方法中，logs 是一个包含有状态度量结果的 dict。\n",
    "\n",
    "### 训练的具体方法\n",
    "此外，对于训练，Keras 还提供了以下方法：\n",
    "1. on_epoch_begin(self, epoch, logs=None)：在一个周期开始时的训练中被调用。\n",
    "2. on_epoch_end(self, epoch, logs=None)：在一个周期结束时的训练中被调用。\n",
    "\n",
    "### 使用日志字典\n",
    "日志 dict 包含损失值，以及批处理或 epoch 结束时的所有度量。下例包括损失和平均绝对误差："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "批次： 0, 损失值 ：  29.97.\n",
      "批次： 1, 损失值 ： 790.62.\n",
      "批次： 2, 损失值 ：  23.57.\n",
      "批次： 3, 损失值 ：  11.89.\n",
      "批次： 4, 损失值 ：   7.27.\n",
      "周期： 0 平均损失值 ： 172.67  平均绝对误差：    8.03.\n",
      "批次： 0, 损失值 ：   7.14.\n",
      "批次： 1, 损失值 ：   7.12.\n",
      "批次： 2, 损失值 ：   4.87.\n",
      "批次： 3, 损失值 ：   6.79.\n",
      "批次： 4, 损失值 ：   4.36.\n",
      "周期： 1 平均损失值 ：   6.06  平均绝对误差：    2.04.\n",
      "批次： 0, 损失值 ：   4.47.\n",
      "批次： 1, 损失值 ：   7.02.\n",
      "批次： 2, 损失值 ：   8.59.\n",
      "批次： 3, 损失值 ：  12.23.\n",
      "批次： 4, 损失值 ：  22.20.\n",
      "周期： 2 平均损失值 ：  10.90  平均绝对误差：    2.62.\n"
     ]
    }
   ],
   "source": [
    "class LossAndErrorPrintingCallback(tf.keras.callbacks.Callback):\n",
    "\n",
    "  def on_train_batch_end(self, batch, logs=None):\n",
    "    print('批次： {}, 损失值 ：{:7.2f}.'.format(batch, logs['loss']))\n",
    "\n",
    "  def on_test_batch_end(self, batch, logs=None):\n",
    "    print('批次： {}, 损失值：{:7.2f}.'.format(batch, logs['loss']))\n",
    "\n",
    "  def on_epoch_end(self, epoch, logs=None):\n",
    "    print('周期： {} 平均损失值 ：{:7.2f}  平均绝对误差： {:7.2f}.'.format(epoch, logs['loss'], logs['mae']))\n",
    "\n",
    "model = get_model()\n",
    "_ = model.fit(x_train, y_train,\n",
    "          batch_size=64,\n",
    "          steps_per_epoch=5,\n",
    "          epochs=3,\n",
    "          verbose=0,\n",
    "          callbacks=[LossAndErrorPrintingCallback()])\n",
    "\n",
    "# 类似地，可以在 evaluate() 调用中提供回调。\n",
    "_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=20,\n",
    "          callbacks=[LossAndErrorPrintingCallback()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Keras 回调应用程序的示例\n",
    "下面的部分将指导你创建简单的回调应用程序。\n",
    "\n",
    "### 提前止损\n",
    "第一个例子展示了创建回调函数的过程，该函数通过改变属性 model.stop_training(boolean) 在达到最小损失时停止 Keras 训练。用户可以提供一个参数（patience）来指定培训在最终停止之前应该等待多少时间段。\n",
    "\n",
    "[tf.keras.callbacks.EarlyStoppint](https://tensorflow.google.cn/api_docs/python/tf/keras/callbacks/EarlyStopping) 提供更完整和通用的实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "批次： 0, 损失值 ：  27.70.\n",
      "批次： 1, 损失值 ：1231.70.\n",
      "批次： 2, 损失值 ：  24.13.\n",
      "批次： 3, 损失值 ：  10.59.\n",
      "批次： 4, 损失值 ：   8.18.\n",
      "周期： 0 平均损失值 ： 260.46  平均绝对误差：    9.22.\n",
      "批次： 0, 损失值 ：   8.70.\n",
      "批次： 1, 损失值 ：   8.07.\n",
      "批次： 2, 损失值 ：   6.23.\n",
      "批次： 3, 损失值 ：   5.78.\n",
      "批次： 4, 损失值 ：   5.14.\n",
      "周期： 1 平均损失值 ：   6.78  平均绝对误差：    2.21.\n",
      "批次： 0, 损失值 ：   4.18.\n",
      "批次： 1, 损失值 ：   4.27.\n",
      "批次： 2, 损失值 ：   4.75.\n",
      "批次： 3, 损失值 ：   4.30.\n",
      "批次： 4, 损失值 ：   4.65.\n",
      "周期： 2 平均损失值 ：   4.43  平均绝对误差：    1.72.\n",
      "批次： 0, 损失值 ：   7.22.\n",
      "批次： 1, 损失值 ：   5.73.\n",
      "批次： 2, 损失值 ：   5.56.\n",
      "批次： 3, 损失值 ：   6.87.\n",
      "批次： 4, 损失值 ：   9.88.\n",
      "周期： 3 平均损失值 ：   7.05  平均绝对误差：    2.16.\n",
      "恢复最佳时期结束时的模型权重\n",
      "周期 00004: 提前停止\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "class EarlyStoppingAtMinLoss(tf.keras.callbacks.Callback):\n",
    "  \"\"\"当损失在最小值时停止训练\n",
    "\n",
    "  参数:\n",
    "      patience: 在这个数字没有改善之后便停止训练。\n",
    "  \"\"\"\n",
    "\n",
    "  def __init__(self, patience=0):\n",
    "    super(EarlyStoppingAtMinLoss, self).__init__()\n",
    "\n",
    "    self.patience = patience\n",
    "\n",
    "    # best_weights 用于存储发生最小损失的权重。\n",
    "    self.best_weights = None\n",
    "\n",
    "  def on_train_begin(self, logs=None):\n",
    "    # 当损失不再是最小值时，它所等待的时间也就不再是最小值了。\n",
    "    self.wait = 0\n",
    "    # 训练的时间止于\n",
    "    self.stopped_epoch = 0\n",
    "    # 将最佳值初始化为无穷大。\n",
    "    self.best = np.Inf\n",
    "\n",
    "  def on_epoch_end(self, epoch, logs=None):\n",
    "    current = logs.get('loss')\n",
    "    if np.less(current, self.best):\n",
    "      self.best = current\n",
    "      self.wait = 0\n",
    "      # 如果当前结果更好（）更少），则记录最佳权重。\n",
    "      self.best_weights = self.model.get_weights()\n",
    "    else:\n",
    "      self.wait += 1\n",
    "      if self.wait >= self.patience:\n",
    "        self.stopped_epoch = epoch\n",
    "        self.model.stop_training = True\n",
    "        print('恢复最佳时期结束时的模型权重')\n",
    "        self.model.set_weights(self.best_weights)\n",
    "\n",
    "  def on_train_end(self, logs=None):\n",
    "    if self.stopped_epoch > 0:\n",
    "      print('周期 %05d: 提前停止' % (self.stopped_epoch + 1))\n",
    "    \n",
    "model = get_model()\n",
    "_ = model.fit(x_train, y_train,\n",
    "          batch_size=64,\n",
    "          steps_per_epoch=5,\n",
    "          epochs=30,\n",
    "          verbose=0,\n",
    "          callbacks=[LossAndErrorPrintingCallback(), EarlyStoppingAtMinLoss()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 学习速率调度\n",
    "模型训练中通常做的一件事是随着周期的推移而改变学习率。Keras 后端公开了可以用来设置变量的get_value API。在本例中，我们将展示如何使用自定义回调来动态更改学习速率。\n",
    "\n",
    "注意：这只是一个示例实现。了解更多常规实现，请参阅 [callbacks.LearningRateScheduler](https://tensorflow.google.cn/api_docs/python/tf/keras/callbacks/LearningRateScheduler)和[keras.optimizers.schedules](https://tensorflow.google.cn/api_docs/python/tf/keras/optimizers/schedules)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "周期 00000: 学习率是 0.1000.\n",
      "批次： 0, 损失值 ：  29.53.\n",
      "批次： 1, 损失值 ：1061.40.\n",
      "批次： 2, 损失值 ：  21.60.\n",
      "批次： 3, 损失值 ：  11.58.\n",
      "批次： 4, 损失值 ：   7.41.\n",
      "周期： 0 平均损失值 ： 226.30  平均绝对误差：    8.72.\n",
      "\n",
      "周期 00001: 学习率是 0.1000.\n",
      "批次： 0, 损失值 ：   9.37.\n",
      "批次： 1, 损失值 ：   5.49.\n",
      "批次： 2, 损失值 ：   5.71.\n",
      "批次： 3, 损失值 ：   5.71.\n",
      "批次： 4, 损失值 ：   5.50.\n",
      "周期： 1 平均损失值 ：   6.36  平均绝对误差：    2.08.\n",
      "\n",
      "周期 00002: 学习率是 0.1000.\n",
      "批次： 0, 损失值 ：   4.40.\n",
      "批次： 1, 损失值 ：   4.12.\n",
      "批次： 2, 损失值 ：   4.39.\n",
      "批次： 3, 损失值 ：   5.71.\n",
      "批次： 4, 损失值 ：   4.06.\n",
      "周期： 2 平均损失值 ：   4.53  平均绝对误差：    1.71.\n",
      "\n",
      "周期 00003: 学习率是 0.0500.\n",
      "批次： 0, 损失值 ：   6.26.\n",
      "批次： 1, 损失值 ：   5.17.\n",
      "批次： 2, 损失值 ：   3.65.\n",
      "批次： 3, 损失值 ：   4.79.\n",
      "批次： 4, 损失值 ：   4.30.\n",
      "周期： 3 平均损失值 ：   4.83  平均绝对误差：    1.72.\n",
      "\n",
      "周期 00004: 学习率是 0.0500.\n",
      "批次： 0, 损失值 ：   4.52.\n",
      "批次： 1, 损失值 ：   4.67.\n",
      "批次： 2, 损失值 ：   3.31.\n",
      "批次： 3, 损失值 ：   5.21.\n",
      "批次： 4, 损失值 ：   5.19.\n",
      "周期： 4 平均损失值 ：   4.58  平均绝对误差：    1.71.\n",
      "\n",
      "周期 00005: 学习率是 0.0500.\n",
      "批次： 0, 损失值 ：   5.02.\n",
      "批次： 1, 损失值 ：   3.59.\n",
      "批次： 2, 损失值 ：   4.94.\n",
      "批次： 3, 损失值 ：   4.80.\n",
      "批次： 4, 损失值 ：   6.61.\n",
      "周期： 5 平均损失值 ：   4.99  平均绝对误差：    1.81.\n",
      "\n",
      "周期 00006: 学习率是 0.0100.\n",
      "批次： 0, 损失值 ：   7.95.\n",
      "批次： 1, 损失值 ：   5.23.\n",
      "批次： 2, 损失值 ：   5.33.\n",
      "批次： 3, 损失值 ：   5.35.\n",
      "批次： 4, 损失值 ：   3.62.\n",
      "周期： 6 平均损失值 ：   5.50  平均绝对误差：    1.88.\n",
      "\n",
      "周期 00007: 学习率是 0.0100.\n",
      "批次： 0, 损失值 ：   4.69.\n",
      "批次： 1, 损失值 ：   3.74.\n",
      "批次： 2, 损失值 ：   4.42.\n",
      "批次： 3, 损失值 ：   3.38.\n",
      "批次： 4, 损失值 ：   5.02.\n",
      "周期： 7 平均损失值 ：   4.25  平均绝对误差：    1.62.\n",
      "\n",
      "周期 00008: 学习率是 0.0100.\n",
      "批次： 0, 损失值 ：   3.12.\n",
      "批次： 1, 损失值 ：   2.99.\n",
      "批次： 2, 损失值 ：   4.83.\n",
      "批次： 3, 损失值 ：   3.94.\n",
      "批次： 4, 损失值 ：   4.17.\n",
      "周期： 8 平均损失值 ：   3.81  平均绝对误差：    1.56.\n",
      "\n",
      "周期 00009: 学习率是 0.0050.\n",
      "批次： 0, 损失值 ：   3.96.\n",
      "批次： 1, 损失值 ：   3.23.\n",
      "批次： 2, 损失值 ：   5.88.\n",
      "批次： 3, 损失值 ：   5.00.\n",
      "批次： 4, 损失值 ：   4.93.\n",
      "周期： 9 平均损失值 ：   4.60  平均绝对误差：    1.68.\n",
      "\n",
      "周期 00010: 学习率是 0.0050.\n",
      "批次： 0, 损失值 ：   3.36.\n",
      "批次： 1, 损失值 ：   3.76.\n",
      "批次： 2, 损失值 ：   3.88.\n",
      "批次： 3, 损失值 ：   4.06.\n",
      "批次： 4, 损失值 ：   3.40.\n",
      "周期： 10 平均损失值 ：   3.69  平均绝对误差：    1.53.\n",
      "\n",
      "周期 00011: 学习率是 0.0050.\n",
      "批次： 0, 损失值 ：   4.10.\n",
      "批次： 1, 损失值 ：   3.74.\n",
      "批次： 2, 损失值 ：   3.07.\n",
      "批次： 3, 损失值 ：   4.41.\n",
      "批次： 4, 损失值 ：   3.97.\n",
      "周期： 11 平均损失值 ：   3.86  平均绝对误差：    1.53.\n",
      "\n",
      "周期 00012: 学习率是 0.0010.\n",
      "批次： 0, 损失值 ：   3.93.\n",
      "批次： 1, 损失值 ：   4.39.\n",
      "批次： 2, 损失值 ：   3.61.\n",
      "批次： 3, 损失值 ：   3.00.\n",
      "批次： 4, 损失值 ：   4.11.\n",
      "周期： 12 平均损失值 ：   3.81  平均绝对误差：    1.54.\n",
      "\n",
      "周期 00013: 学习率是 0.0010.\n",
      "批次： 0, 损失值 ：   3.87.\n",
      "批次： 1, 损失值 ：   3.85.\n",
      "批次： 2, 损失值 ：   4.97.\n",
      "批次： 3, 损失值 ：   4.91.\n",
      "批次： 4, 损失值 ：   4.71.\n",
      "周期： 13 平均损失值 ：   4.46  平均绝对误差：    1.64.\n",
      "\n",
      "周期 00014: 学习率是 0.0010.\n",
      "批次： 0, 损失值 ：   4.59.\n",
      "批次： 1, 损失值 ：   4.31.\n",
      "批次： 2, 损失值 ：   3.45.\n",
      "批次： 3, 损失值 ：   3.49.\n",
      "批次： 4, 损失值 ：   3.78.\n",
      "周期： 14 平均损失值 ：   3.93  平均绝对误差：    1.59.\n"
     ]
    }
   ],
   "source": [
    "class LearningRateScheduler(tf.keras.callbacks.Callback):\n",
    "  \"\"\"学习速率调度程序，根据调度来设置学习速率。\n",
    "\n",
    "  参数:\n",
    "      schedule:以开始索引（整数，从0开始索引）和当前学习率作为输入，\n",
    "                          并返回新的学习率作为输出（浮点）的函数。\n",
    "  \"\"\"\n",
    "\n",
    "  def __init__(self, schedule):\n",
    "    super(LearningRateScheduler, self).__init__()\n",
    "    self.schedule = schedule\n",
    "\n",
    "  def on_epoch_begin(self, epoch, logs=None):\n",
    "    if not hasattr(self.model.optimizer, 'lr'):\n",
    "      raise ValueError('Optimizer must have a \"lr\" attribute.')\n",
    "    # 从模型的优化器中获得当前的学习率。\n",
    "    lr = float(tf.keras.backend.get_value(self.model.optimizer.lr))\n",
    "    # 调用调度函数获得调度的学习速率。\n",
    "    scheduled_lr = self.schedule(epoch, lr)\n",
    "    # 在这个 epoch 开始之前将值设置回优化器\n",
    "    tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)\n",
    "    print('\\n周期 %05d: 学习率是 %6.4f.' % (epoch, scheduled_lr))\n",
    "    \n",
    "\n",
    "LR_SCHEDULE = [\n",
    "    # （起始时间，学习率）元组\n",
    "    (3, 0.05), (6, 0.01), (9, 0.005), (12, 0.001)\n",
    "]\n",
    "\n",
    "def lr_schedule(epoch, lr):\n",
    "  \"\"\"辅助函数来检索基于 epoch 的预定学习率\"\"\"\n",
    "  if epoch < LR_SCHEDULE[0][0] or epoch > LR_SCHEDULE[-1][0]:\n",
    "    return lr\n",
    "  for i in range(len(LR_SCHEDULE)):\n",
    "    if epoch == LR_SCHEDULE[i][0]:\n",
    "      return LR_SCHEDULE[i][1]\n",
    "  return lr\n",
    "\n",
    "model = get_model()\n",
    "_ = model.fit(x_train, y_train,\n",
    "          batch_size=64,\n",
    "          steps_per_epoch=5,\n",
    "          epochs=15,\n",
    "          verbose=0,\n",
    "          callbacks=[LossAndErrorPrintingCallback(), LearningRateScheduler(lr_schedule)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 标准Keras回调\n",
    "一定要通过访问 API 文档来检查现有的 Keras 回调。应用程序包括记录到CSV、保存模型、在TensorBoard上可视化等等。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
